// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.ads.dfa.matchtables;

import com.google.api.ads.dfa.axis.factory.DfaServices;
import com.google.api.ads.dfa.lib.client.DfaSession;
import com.google.api.services.dfareporting.Dfareporting;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * Tokenizes DT file lines, passes arrays of Strings to array of LookupObjects, maps specified
 * match tables to implementations of LookupObject.
 *
 * @author Moshe Kahan
 */
class LookupObjectManager implements DataTransferFileParser {

  /**
   * Factory method accepts DFA service/session objects needed to create SOAP remotes to the
   * API.  These are passed to the individual LookupObjects.  Array of lookup objects configured
   * based on set of MatchTables that are enabled in the TableSettings object.
   *
   * @param dfaServices DFA services object needed to create SOAP remotes to the API
   * @param session DFA session object needed to create SOAP remotes to the API  
   * @param tableSettings specifies what tables and fields to generate along with delimiters used.
   */
  static LookupObjectManager getLookupObjectManager(DfaServices dfaServices, DfaSession session,
      Dfareporting dfaReporting, Long userProfileId, TableSettings tableSettings) {
    PaidSearchLookup paidSearchLookup = null;
    List<LookupObject> lookupObjectArray = new ArrayList<LookupObject>();
    if (tableSettings.hasMatchTable(MatchTable.Type.ADVERTISER) ||
        tableSettings.hasMatchTable(MatchTable.Type.ACTIVITY_CAT) ||
        tableSettings.hasMatchTable(MatchTable.Type.ACTIVITY_TYPE) ||
        tableSettings.hasMatchTable(MatchTable.Type.USER_DEFINED_SPOT_VARS)) {
      lookupObjectArray.add(new AdvertiserLookup(dfaServices, session, tableSettings));
    }
    if (tableSettings.hasMatchTable(MatchTable.Type.CAMPAIGN)) {
      lookupObjectArray.add(new CampaignLookup(dfaServices, session, tableSettings));
    }
    if (tableSettings.hasMatchTable(MatchTable.Type.AD) ||
        tableSettings.hasMatchTable(MatchTable.Type.AD_PAGE_ASSIGNMENTS) ||
        tableSettings.hasMatchTable(MatchTable.Type.CREATIVE) ||
        tableSettings.hasMatchTable(MatchTable.Type.CREATIVE_AD_ASSIGNMENTS) ||
        tableSettings.hasMatchTable(MatchTable.Type.CUSTOM_CREATIVE_FIELDS) ||
        tableSettings.hasMatchTable(MatchTable.Type.MET_EVENT) ||
        tableSettings.hasMatchTable(MatchTable.Type.KEYVAL)) {
      lookupObjectArray.add(new AdCreativeLookup(dfaServices, session, tableSettings));
    }
    if (tableSettings.hasMatchTable(MatchTable.Type.SITE)) {
      lookupObjectArray.add(new SiteLookup(dfaServices, session, tableSettings));
    }
    if (tableSettings.hasMatchTable(MatchTable.Type.PAGE) ||
        tableSettings.hasMatchTable(MatchTable.Type.PAGE_COST) ||
        tableSettings.hasMatchTable(MatchTable.Type.PAGE_FLIGHT_COST)) {
      lookupObjectArray.add(new PlacementLookup(dfaServices, session, tableSettings));
    }
    if (tableSettings.hasMatchTable(MatchTable.Type.COUNTRIES) ||
        tableSettings.hasMatchTable(MatchTable.Type.STATES) ||
        tableSettings.hasMatchTable(MatchTable.Type.DESIGNATED_MARKET_AREA) ||
        tableSettings.hasMatchTable(MatchTable.Type.CITIES) ||
        tableSettings.hasMatchTable(MatchTable.Type.BROWSERS) ||
        tableSettings.hasMatchTable(MatchTable.Type.OPERATING_SYSTEMS)) {
      lookupObjectArray.add(new GeoTechLookup(dfaServices, session, tableSettings));
    }
    if (tableSettings.hasMatchTable(MatchTable.Type.DARTSEARCH)) {
      // For speed reasons we need to process the paid search lookup separately.
      paidSearchLookup = new PaidSearchLookup(dfaReporting, userProfileId, tableSettings);
      lookupObjectArray.add(paidSearchLookup);
    }
    return new LookupObjectManager(tableSettings.getInputDelimiter(), lookupObjectArray,
        paidSearchLookup);
  }

  LookupObjectManager(String regexDelimiter, List<LookupObject> lookupObjectArray,
      PaidSearchLookup paidSearchLookup) {
    this.lookupObjectArray = lookupObjectArray;
    this.paidSearchLookup = paidSearchLookup;
    this.regexDelimiter = regexDelimiter;
    headerArray = null;
  }

  /**
   * Getter for regex delimiter.
   * @return delimiter used to split DT file lines.
   */
  String getRegexDelimiter() {
    return regexDelimiter;
  }

  /**
   * Retrieve the number of header columns.
   * 
   * @return the number of header columns; 0 if the headers are in an un-processed state
   */
  int getHeaderColumns() {
    if (headerArray != null) {
      return headerArray.length;
    }
    return 0;
  }

  /**
   * Clears header information about the last DT file parsed in preparation for the next one.
   */
  public void clearHeaders() {
    for (LookupObject lookupObject : lookupObjectArray) {
      lookupObject.clearHeaders();
    }
    headerArray = null;
  }

  /**
   * Parses the header row of a Data Transfer file
   * @param line Data Transfer header file row
   * @throws DataTransferFileException if error encountered processing DT file
   */
  public int parseHeader(String line) throws DataTransferFileException {
    headerArray = line.split(Pattern.quote(regexDelimiter));
    // One value in array probably means we have the wrong delimiter.
    if (headerArray.length < 2) {
      System.out.println("Possible invalid file or incorrect file delimiter.");
      // Heuristic to determine likely delimiter.
      int delimiterIndexGuess = line.indexOf("-ID") + 3;
      if (delimiterIndexGuess < line.length()) {
        char delimiterGuess = line.charAt(delimiterIndexGuess);
        throw new DataTransferFileException(regexDelimiter + " seems to be an invalid delimter"
            + " for this DT file. Best guess for correct delimiter:" + delimiterGuess
            + " with hex value:" +  Integer.toHexString(delimiterGuess));
      }
    }

    // Let each active LookupObject register the column indices for all lookup fields.
    int index = 0;
    for (String columnHeader : headerArray) {
      LookupObject.LookupField lookupField = LookupObject.LookupField.getByFieldName(columnHeader);
      if (lookupField != null) {
        for (LookupObject lookupObject : lookupObjectArray) {
          lookupObject.addHeader(index, columnHeader);
        }
      }
      ++index;
    }
    return getHeaderColumns();
  }

  /**
   * Parses a data row of a Data Transfer file
   * @param line Data Transfer file row
   * @throws DataTransferFileException if error encountered processing DT file
   */
  public void parse(String line) throws DataTransferFileException {
    String[] stringArray = line.split(Pattern.quote(regexDelimiter), -1);

    // Sanity check that this row has the same number of columns.
    if (stringArray.length != headerArray.length) {
      throw new DataTransferFileException("DT file line not split correctly. Expected "
          + headerArray.length 
          + " columns but got instead " 
          + stringArray.length
          + ". Line: " 
          + line);
    }

    // Send array to all active LookupObjects.
    for (LookupObject lookupObject : lookupObjectArray) {
      lookupObject.addValues(stringArray);
    }
  }

  /**
   * Trigger generateMatchTables for all active LookupObjects.
   * @param outputPath path to where match table file should be written
   * @throws MatchTableFileException  if error encountered generating MT file
   * @throws DfaRemoteException if error encountered doing Remote call
   */
  void generateMatchTables(String outputPath) throws MatchTableFileException, DfaRemoteException {
    if (paidSearchLookup != null) {
      paidSearchLookup.preGenerateMatchTable();
    }
    for (LookupObject lookupObject : lookupObjectArray) {
      lookupObject.generateMatchTable(outputPath);
    }
  }

  private List<LookupObject> lookupObjectArray;
  private PaidSearchLookup paidSearchLookup;
  private String regexDelimiter;
  private String[] headerArray;
}
